const fs = require('fs')
const path = require('path')
const express = require('express')
const favicon = require('serve-favicon')
const morgan = require('morgan')
const cookieParser = require('cookie-parser')
const bodyParser = require('body-parser')
require('./common/mongodb')
// const expressSession = require('express-session')
// const redisClient = require('./common/redis').client
// const RedisStore = require('connect-redis')(expressSession)
const swaggerUi = require('swagger-ui-express')
const YAML = require('yamljs')
const swaggerDocument = YAML.load('./docs/swagger.yaml')
const helmet = require('helmet')
const jwt = require('jsonwebtoken')
const refreshTokenById = require('./controller/auth').refreshTokenById

// routes import
const index = require('./routes/index')
const auth = require('./routes/auth')
const users = require('./routes/users')
const teams = require('./routes/teams')
const announcements = require('./routes/announcements')
const resources = require('./routes/resources')
const votes = require('./routes/votes')

const app = express()

const accessLogStream = fs.createWriteStream(path.join(__dirname, 'log', 'access.log'), { flags: 'a' })

/**************
 * Middleware *
 *************/
app.use(helmet())
app.use(favicon(path.join(__dirname, 'static', 'favicon.ico')))
app.use(morgan('common', { stream: accessLogStream }))
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))
app.use(cookieParser())
app.use(express.static(path.join(__dirname, 'dist')))
app.use(express.static(path.join(__dirname, 'static')))
app.use(express.static(path.join(__dirname, 'docs')))
// app.use(expressSession({
//   store: new RedisStore({ client: redisClient }), // 还有很多兼容存储列表
//   // cookie: {maxAge: 2 * 60 * 60 * 1000}, // 跟expires一样可以用来控制失效时间，这个指定的话会再算一遍失效时间点，而expires是直接指定失效时间点，两者最后定义的会被录用，没有指定的话，大多数浏览器会认为浏览器关掉则删除
//   secret: 'aranciacheng', // 必填项，用来签名seesion ID cookie
//   resave: false, // 每次拿回来都重新保存一下，即使没有变，在多个同一session并行请求时可能导致写覆盖
//   saveUninitialized: false, // 未初始化的session保存到store中，选择false有利于：①登录session，②减少服务器store使用；③设置cookie之前遵守需要许可的法律
//   name: 'ssas.sid' // 浏览器端对应存储cookie的名字，即key，最好改为我们自己的，不采用默认
// }))

// connect-redis 的ttl选项可以设置在redis中过期时间。Defaults to session.cookie.maxAge (if set), or one day.
// 对于一个全新的（没有任何express-session相关cookie）客户端发过来的请求，都会在后台逻辑中通过req.session.id得到的不一样的id，只要我们没有req.session.sth设置其他的key下去，响应头就不会set-cookie
// 在controller层设置req.session.sthKey = sthVal就会使得发出的响应头要求设置对应的sessionID的cookie
// req.session既可拿到session的序列化为JSON的object，同时可以设置对应想要的字段，比如额外设置一个views字段，req.session.view
// req.session.save() 把内存中的数据存储回DB中
// req.session.id req.sessionID的alias
// req.sessionID 是一个只读属性，在created或者loaded的时候被设置
// 如果旧cookie中的sessionID没有在对应store中找到对应session，则直接会新建一个session，即req.session.id会拿到一个新id
app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument, {
  swaggerOptions: {
    validatorUrl: null
  }
}))

// Access Control, Token Required Expected Auth Module
// app.use(function (req, res, next) {
//   const whiteList = ['/token', '/users/retrieval', '/users/verification/code', '/users']
//   const inWhiteList = whiteList.some(path => req.path === '/api' + path)
//   if (!inWhiteList && !req.session.token) {
//     res.status(401).end()
//   } else {
//     next()
//   }
// })

// Access Control with Access Token, inject decoded in req.user
app.use(function(req, res, next) {
  const whiteList = ['/authorization/code', '/authorization/token', '/users/retrieval', '/users/verification/code', '/users']
  const inWhiteList = whiteList.some(path => req.path === path)
  if (inWhiteList) {
    next()
  } else {
    const accessToken = req.cookies.X_SSAS_AccessToken || req.cookies.x_ssas_accesstoken || req.headers['x-ssas-accesstoken']
    if (accessToken) {
      try {
        const decoded = jwt.verify(accessToken, 'SSAS.COM.PRIVATE-KEY')
        // console.log(`[VALID ACCESS TOKEN]${decoded}`)
        req.user = decoded
        next()
      } catch (err) {
        // console.log(`[ERROR AC TOKEN]${err.message}`)
        next(err)
      }
    } else {
      res.status(401).end()
    }
  }
})

// auto refresh Access Token if valid Refresh Token given
app.use(async function(err, req, res, next) {
  if (err && err.name === 'TokenExpiredError') {
    // console.log('[EXPIRE AC TOKEN]')
    const refreshToken = req.cookies.X_SSAS_RefreshToken || req.cookies.x_ssas_refreshtoken || req.headers['x-ssas-refreshtoken']
    if (refreshToken) {
      try {
        const { userId, teamId } = jwt.verify(refreshToken, 'SSAS.COM.PRIVATE-KEY')
        const { accessToken, roles } = await refreshTokenById(userId, teamId)
        res.set({
          'Cache-Control': 'no-store',
          'Pragma': 'no-cache'
        })
        req.user = { userId, roles, teamId }
        // res.set({ 'X-SSAS-AccessTokenReset': accessToken })
        res.cookie('X_SSAS_AccessToken', accessToken, { httpOnly: true })
        next()
      } catch (err) {
        res.status(401).end()
      }
    } else {
      res.status(401).end()
    }
  } else if (err) {
    res.status(401).end()
  } else {
    next()
  }
})


// set routes
app.use('/', index)
app.use('/', auth)
app.use('/', users)
app.use('/', teams)
app.use('/', announcements)
app.use('/', resources)
app.use('/', votes)

// catch 404 and forward to error handler
app.use(function (req, res, next) {
  console.log('in 404')
  let err = new Error('Not Found')
  err.status = 404
  next(err)
})

// error handler
app.use(function (err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message
  res.locals.error = req.app.get('env') === 'development' ? err : {}

  // render the error page
  res.status(err.status || 500)
  res.send('error')
})

module.exports = app
